/** 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package it.free.i.management;

import it.free.i.naming.NamingBuilder;
import it.free.i.virtual.machine.hal.devicealias.DeviceAlias;
import it.free.i.virtual.machine.hal.devicealias.port.DeviceAliasPort;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.util.Properties;
import java.util.Set;

import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.DynamicMBean;
import javax.management.InstanceAlreadyExistsException;
import javax.management.InvalidAttributeValueException;
import javax.management.JMRuntimeException;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MBeanServerConnection;
import javax.management.MBeanServerFactory;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;
import javax.naming.NameNotFoundException;

import org.apache.log4j.Logger;

/**  
 * Jmx server implementation
 * @author stefano
 */
public final class JmxRegisterServer implements DynamicMBean {

	
	private static JmxRegisterServer JMX_SERVER_INSTANCE = null;
	private static final long serialVersionUID = 1206637208827008757L;
	private static final String MBEAN_SERVER_DOMAIN = "jmx.server.domain";
	private static final String MBEAN_SERVER_URL = "jmx.server.url";
	private static Logger logger = null;
	
	private Properties myConfiguration = null;
	private MBeanServer mBeanServer = null;
	private JMXConnectorServer jmxConnectorServer = null;
	private String domain = null;
	private String url = null;
	
	static{
		logger = Logger.getLogger(JmxRegisterServer.class);
	}
	
	public void setup(Properties configuration){
		this.myConfiguration = configuration;
	}
	
	/**
	 * Private as for a singleton
	 */
	private JmxRegisterServer() {
		super();
	}
	
	/**
	 * Factory singleton private instance accesor
	 * @return THE instance of this factory
	 */
	public static JmxRegisterServer getInstance(){
		if(JMX_SERVER_INSTANCE == null){
		   synchronized (JmxRegisterServer.class){
			   if(JMX_SERVER_INSTANCE == null){
				   JMX_SERVER_INSTANCE = new JmxRegisterServer(); 
			   }	   
		   }
		}
		return JMX_SERVER_INSTANCE;
	}

	/**
	 * Create a register server for mbean
	 * @throws JMRuntimeException
	 */
	public void startRegisterServer() throws JMRuntimeException {
		
		try{           
			domain = this.myConfiguration.getProperty(MBEAN_SERVER_DOMAIN);
		    url = this.myConfiguration.getProperty(MBEAN_SERVER_URL);
			
		    if( (domain == null) || (domain.trim().length() == 0) ||
		    	(url == null) || (url.trim().length() == 0) ){
		    	throw (new JMRuntimeException("Management configuration missing"));
		    }
		    
		    mBeanServer = MBeanServerFactory.createMBeanServer(domain); 
		    //Test with [service:jmx:rmi:///jndi/rmi://localhost:9999/server]
			
		    JMXServiceURL jmxUrl =  new JMXServiceURL(url);
		    jmxConnectorServer = JMXConnectorServerFactory.newJMXConnectorServer(jmxUrl, null, mBeanServer); 
		    
		    jmxConnectorServer.start();
		    
		}catch(MalformedURLException muExc) {
			   throw (new JMRuntimeException(muExc.getMessage()));
		}catch(IOException ioExc) {
			   throw (new JMRuntimeException(ioExc.getMessage()));
		}/*catch(MalformedObjectNameException monExc) {
			   throw (new JMRuntimeException(monExc.getMessage()));
		} */catch(NullPointerException npExc) {
			   throw (new JMRuntimeException(npExc.getMessage()));
		}
	}
	
	public void scanRegisterServer() throws JMRuntimeException {
		
		try{           
			domain = this.myConfiguration.getProperty(MBEAN_SERVER_DOMAIN);
		    url = this.myConfiguration.getProperty(MBEAN_SERVER_URL);
			
		    if( (domain == null) || (domain.trim().length() == 0) ||
		    	(url == null) || (url.trim().length() == 0) ){
		    	throw (new JMRuntimeException("Management configuration missing"));
		    }
		    
		    //          "service:jmx:rmi:///jndi/rmi://host:1099/jrmpconnector_jonas"
		    //Test with [service:jmx:rmi:///jndi/rmi://localhost:1099/server]
			
		    JMXServiceURL jmxUrl =  new JMXServiceURL(url); 
		    JMXConnector myJmxConnector = JMXConnectorFactory.newJMXConnector(jmxUrl, null);
		    
		    myJmxConnector.connect();
		    
		    MBeanServerConnection myMBeanServerConnection = myJmxConnector.getMBeanServerConnection();
		    
		    
		    Set<ObjectName> names = myMBeanServerConnection.queryNames(null, null);
		    
		    if(names != null){
		       logger.info("MBean found: " + names.size());
		       
		       for(ObjectName aName: names){
		    	   logger.info("Object found, name: " + aName.getCanonicalName() + " " + aName.getDomain());
		       }
		       
		    }
		    
		}catch(MalformedURLException muExc) {
			   throw (new JMRuntimeException(muExc.getMessage()));
		}catch(IOException ioExc) {
		       Throwable throwable = ioExc.getCause();
		       if(throwable instanceof NameNotFoundException){
		    	  logger.info("Server MBean not mapped yet");   
		       }else{
		    	     throw (new JMRuntimeException(ioExc.getMessage()));
		       }
		}catch(NullPointerException npExc) {
			   throw (new JMRuntimeException(npExc.getMessage()));
		}
		
		
	}
	
	/**
	 * Register a device alias as a mbean
	 * @param deviceAlias is the alias to register
	 * @throws JMRuntimeException
	 */
	public void registerBean(DeviceAlias deviceAlias) throws JMRuntimeException{
		
		if(this.mBeanServer == null){
		   logger.error("Can't register object before initialize, do start before, please.");
		   return;
		}else if(deviceAlias == null){
				 logger.error("Can't register a null device alias.");
			     return;
		}

		//Construct the ObjectName for the MBean to register
		ObjectName mBeanName = null;
		String jmxName = null;
		try{
			jmxName = NamingBuilder.buildJMXName(domain, deviceAlias.getName());
			mBeanName = new ObjectName(jmxName);
			mBeanServer.registerMBean(deviceAlias, mBeanName);
			logger.info("Jmx bean [" + mBeanName + "] succesfully registered");
			
		}catch(MalformedObjectNameException monExc) {
			   throw (new JMRuntimeException(monExc.getMessage()));
		}catch(NullPointerException npEx) {
			   throw (new JMRuntimeException(npEx.getMessage()));
		}catch(InstanceAlreadyExistsException iaeExc) {
			   throw (new JMRuntimeException(iaeExc.getMessage()));
		}catch(MBeanRegistrationException mrExc) {
			   throw (new JMRuntimeException(mrExc.getMessage()));
		}catch(NotCompliantMBeanException nceExc) {
			   throw (new JMRuntimeException(nceExc.getMessage()));
		}
	}
	
	/**
	 * Register a device alias as a mbean
	 * @param deviceAliasPort is the alias to register
	 * @throws JMRuntimeException
	 */
	public void registerBean(DeviceAliasPort deviceAliasPort) throws JMRuntimeException{
		
		if(this.mBeanServer == null){
		   logger.error("Can't register object before initialize, do start before, please.");
		   return;
		}else if(deviceAliasPort == null){
				 logger.error("Can't register a null device alias port.");
			     return;
		}

		//Construct the ObjectName for the MBean to register
		ObjectName mBeanName = null;
		String jmxName = null;
		try{
			jmxName = NamingBuilder.buildJMXName(domain, deviceAliasPort.getName());
			mBeanName = new ObjectName(jmxName);
			mBeanServer.registerMBean(deviceAliasPort, mBeanName);
			logger.info("Jmx bean [" + mBeanName + "] succesfully registered");
			
		}catch(MalformedObjectNameException monExc) {
			   throw (new JMRuntimeException(monExc.getMessage()));
		}catch(NullPointerException npEx) {
			   throw (new JMRuntimeException(npEx.getMessage()));
		}catch(InstanceAlreadyExistsException iaeExc) {
			   throw (new JMRuntimeException(iaeExc.getMessage()));
		}catch(MBeanRegistrationException mrExc) {
			   throw (new JMRuntimeException(mrExc.getMessage()));
		}catch(NotCompliantMBeanException nceExc) {
			   throw (new JMRuntimeException(nceExc.getMessage()));
		}
	}

	/**
	 * @see javax.management.DynamicMBean
	 */
	public Object getAttribute(String attribute)
			throws AttributeNotFoundException, MBeanException,
			ReflectionException {
		return null;
	}

	/**
	 * @see javax.management.DynamicMBean
	 */
	public AttributeList getAttributes(String[] attributes) {
		return null;
	}

	/**
	 * @see javax.management.DynamicMBean
	 */
	public MBeanInfo getMBeanInfo() {
		return JmxFacilities.buildMBeanInfo(this.getClass(), "");
	}

	/**
	 * @see javax.management.DynamicMBean
	 */
	public Object invoke(String actionName, 
						 Object[] params, 
						 String[] signature) throws MBeanException, ReflectionException {
		
			Object invcationReturnValue = null;
			
			try{
	            Method method = this.getClass().getMethod(actionName, new Class[params.length]);
	            invcationReturnValue = method.invoke(this, params);
	            logger.info("Invoked action [" + actionName + "] invoked method [" + method.getName() + "]");
	        }catch(NoSuchMethodException nsmExc) {    
	               throw (new MBeanException(nsmExc));
	        }catch(SecurityException sExc) {
	               throw (new MBeanException(sExc));
	         }catch(IllegalArgumentException iaExc) {
	            	throw (new MBeanException(iaExc));
	         }catch(IllegalAccessException iaExc) {
	            	throw (new MBeanException(iaExc));    
	         }catch(InvocationTargetException itExc) {
	            	throw (new MBeanException(itExc));
	        }
	        
	        return invcationReturnValue;
	}

	/**
	 * @see javax.management.DynamicMBean
	 */
	public void setAttribute(Attribute attribute)
			throws AttributeNotFoundException, InvalidAttributeValueException,
			MBeanException, ReflectionException {
	}

	/**
	 * @see javax.management.DynamicMBean
	 */
	public AttributeList setAttributes(AttributeList attributes) {
		return null;
	}
}
